package linuxbox.util;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import linuxbox.*;
import linuxbox.common.*;
import linuxbox.exception.*;
import org.apache.log4j.*;
import org.jdom.*;
import org.jdom.input.*;

public class BoxModule {
    protected static Logger logger =
        Logger.getLogger(BoxModule.class.getName());

    Map actionMap = new HashMap();
    Map settings = new HashMap(); //Keeps the module settings
    Map init_settings = new HashMap(); //Keeps the init parameters
    Map homepageModules = new HashMap();
    //Keeps the modules that can be displayed in home page
    Map adminModules = new HashMap();
    //Keeps the modules that can be displayed in Admin page

    String moduleName = null; //The key to look for module in module list
    String category = null;
    String version = null;
    boolean required = false;
    String fullName = null;
    String url = null;
    String icon = null;

    public BoxModule(Element mod) throws LinuxboxException {
        parseModule(mod);
    }

    public String getName() {
        return moduleName;
    }

    public String getFullName() {
        return fullName;
    }

    public String getURL() {
        return url;
    }

    public String getCategory() {
        return category;
    }

    public void parseModule(Element mod) throws LinuxboxException {
        String load = mod.getAttributeValue("load");
        if ("no".equalsIgnoreCase(load))
            return;

        String fn = mod.getTextTrim();
        fn = BoxStatic.configPath + fn;

        try {
            SAXBuilder builder = new SAXBuilder(false);
            Document doc = builder.build(new FileInputStream(fn));
            Element root = doc.getRootElement();

            moduleName = root.getAttributeValue("name");
            version = root.getAttributeValue("version");
            required =
                (Boolean.valueOf(root.getAttributeValue("required")))
                .booleanValue();

            Element e = root.getChild("category");
            if (e != null)
                category = e.getTextTrim();

            e = root.getChild("fullname");
            if (e != null)
                fullName = e.getTextTrim();

            e = root.getChild("url");
            if (e != null)
                url = processURL(e);

            e = root.getChild("icon");
            if (e != null)
                icon = e.getTextTrim();

            Element homepage = root.getChild("HomepageModules");
            if (homepage != null)
                processHomepageModules(homepage);
            else if (logger.isDebugEnabled())
                logger.debug(
                             "No HomepageModules found for module:" + moduleName);

            Element initParam = root.getChild("InitParams");
            if (initParam != null)
                processInitParameters(initParam);
            else if (logger.isDebugEnabled())
                logger.debug("No InitParams found for module:" + moduleName);

            Element admins = root.getChild("AdminModules");
            if (admins != null)
                processAdminModules(admins);
            else if (logger.isDebugEnabled())
                logger.debug("No AdminModules found for module:" + moduleName);

            Element actionList = root.getChild("ActionList");
            if (actionList != null)
                processActionList(actionList);
            else if (logger.isDebugEnabled())
                logger.debug("No ActionList found for module:" + moduleName);

        } catch (JDOMException exc) {
            throw new LinuxboxException(exc.getMessage());
        } catch (FileNotFoundException fnfe) {
            throw new LinuxboxException("can not find module xml file=" + fn);
        } catch (Exception ex) {
            logger.debug("unknown error:" + ex.getMessage());
        }
    }

    protected void processActionList(Element actionList)
        throws LinuxboxException {
        List actionDefs = actionList.getChildren("Action");
        if (actionDefs == null)
            return;
        for (Iterator it = actionDefs.iterator(); it.hasNext();) {
            Element tmpEl = (Element) it.next();
            String name = tmpEl.getAttributeValue("name");
            String classPath = tmpEl.getAttributeValue("class");

            Object actionObj = null;
            try {
                logger.debug("Attempting to load class " + classPath);

                Class c = Class.forName(classPath);
                actionObj = c.newInstance();
            } catch (ClassNotFoundException exc) {
                throw new LinuxboxException(exc.getMessage());
            } catch (InstantiationException ie) {
                throw new LinuxboxException(ie.getMessage());
            } catch (IllegalAccessException iae) {
                throw new LinuxboxException(iae.getMessage());
            }

            List paramList = tmpEl.getChildren("Param");
            setBeanProperties(actionObj, paramList);

            if (actionMap.get(name) != null) {
                //warn the user
                logger.warn("There is already an action - " + name);
            }

            logger.debug("put " + actionObj + " into " + name);
            actionMap.put(name, actionObj);
        }
    }

    protected void processInitParameters(Element paramList)
        throws LinuxboxException {
        List paramDefs = paramList.getChildren("Param");
        if (paramDefs == null)
            return;
        for (Iterator it = paramDefs.iterator(); it.hasNext();) {
            Element e = (Element) it.next();
            String category = e.getAttributeValue("category");
            String name = e.getAttributeValue("name");
            String type = e.getAttributeValue("type");
            String value = e.getText();

            //Use String[] to represent a collection of strings
            //we may need to do some type checking here.
            //TODOXX:
            if (type.compareToIgnoreCase("String[]") == 0) {
                //String [] parameter is a string seperated by ","
                StringTokenizer s = new StringTokenizer(value, ",");
                LinkedList l = new LinkedList();
                while (s.hasMoreTokens()) {
                    l.add(s.nextToken().trim());
                }
                String[] values = new String[l.size()];
                l.toArray(values);
                setInitConfigValues(category, name, values);
            } else {
                setInitConfigValue(category, name, value);
            }
        }
    }

    protected void processHomepageModules(Element ModList)
        throws LinuxboxException {
        List moduleDefs = ModList.getChildren("module");
        if (moduleDefs == null)
            return;
        for (Iterator it = moduleDefs.iterator(); it.hasNext();) {
            Element tmpEl = (Element) it.next();
            String name = tmpEl.getAttributeValue("name");
            //The homepage url will be used in jsp:include, which requires
            // relative URL
            //      String url = processURL(tmpEl);
            String url = tmpEl.getTextTrim();
            if (logger.isDebugEnabled())
                logger.debug("Adding homepage module " + name + "=" + url);
            homepageModules.put(name, url);
        }
    }

    protected void processAdminModules(Element ModList)
        throws LinuxboxException {
        List moduleDefs = ModList.getChildren("module");
        if (moduleDefs == null)
            return;
        for (Iterator it = moduleDefs.iterator(); it.hasNext();) {
            Element tmpEl = (Element) it.next();
            String name = tmpEl.getAttributeValue("name");

            //We could allow multiple rights seperated by ","
            String rights = tmpEl.getAttributeValue("accessRights");
            long lRights = 0;
            StringTokenizer s = new StringTokenizer(rights, ",");
            while (s.hasMoreTokens()) {
                long l = Long.parseLong(s.nextToken().trim(), 16);
                lRights |= l;
            }

            String url = processURL(tmpEl);
            if (logger.isDebugEnabled())
                logger.debug("Adding admin module " + name + "=" + url);
            adminModules.put(name, new BoxAdminModule(name, url, lRights));
        }
    }

    protected String processURL(Element e) {
        String url = e.getTextTrim();
        String urlType = e.getAttributeValue("urltype");
        if (urlType != null) {
            if (urlType.compareTo("jsp") == 0)
                url = BoxStatic.jspDir + url;
            else if (urlType.compareTo("servlet") == 0)
                url = BoxStatic.servletDir + url;
        }
        return url;
    }

    private void setBeanProperties(Object actionObj, List aList)
        throws LinuxboxException {
        Class c = actionObj.getClass();
        Class[] paramTypes = { String.class };

        try {
            for (Iterator it = aList.iterator(); it.hasNext();) {
                Element e = (Element) it.next();
                String methodName = e.getAttributeValue("desc");
                methodName =
                    "set"
                    + methodName.substring(0, 1).toUpperCase()
                    + methodName.substring(1);

                Method m = c.getMethod(methodName, paramTypes);

                Object[] params = { e.getText()};
                m.invoke(actionObj, params);
            }
        } catch (Exception exc) {
            throw new LinuxboxException(exc.getMessage());
        }
    }

    public void setServlet(ActionServlet servlet) {
        for (Iterator it = actionMap.values().iterator(); it.hasNext();) {
            Action act = (Action) it.next();
            act.setServlet(servlet);
        }
    }

    public Action getAction(String name) {
        return (Action) actionMap.get(name);
    }

    public void setInitConfigValue(
                                   String category,
                                   String name,
                                   String value) {
        Map map = getConfigTable(category, init_settings);
        if (logger.isDebugEnabled())
            logger.debug(
                         "Setting init parameter for:"
                         + moduleName
                         + ":"
                         + category
                         + ":"
                         + name
                         + "="
                         + value);
        map.put(name, value);
    }

    public String getInitConfigValue(String category, String name) {
        Map map = getConfigTable(category, init_settings);
        return (String) map.get(name);
    }

    public void setInitConfigValues(
                                    String category,
                                    String name,
                                    String[] value) {
        Map map = getConfigTable(category, init_settings);
        if (logger.isDebugEnabled())
            logger.debug(
                         "Setting init parameter for:"
                         + moduleName
                         + ":"
                         + category
                         + ":"
                         + name
                         + "="
                         + value);
        map.put(name, value);
    }

    public String[] getInitConfigValues(String category, String name) {
        Map map = getConfigTable(category, init_settings);
        return (String[]) map.get(name);
    }

    public String getConfigValue(String category, String name) {
        // First get from settings table
        Map map = getConfigTable(category, settings);
        String val = (String) map.get(name);
        if (val != null)
            return val;

        // if the cache doesn't have it yet, get from the database
        try {
            if (logger.isDebugEnabled()) {
                logger.debug(
                             "Try to get from module config: "
                             + moduleName
                             + " : "
                             + category
                             + " : "
                             + name);
            }
            val =
                (String) BoxConfigUtil.getModuleConfigValue(
                                                        moduleName,
                                                        category,
                                                        name);
        } catch (Exception e) {
            val = null;
        }
        if (val == null) {
            // not in databse then we will use the init value
            if (logger.isDebugEnabled()) {
                logger.debug(
                             "Try to get from module init parameter: "
                             + moduleName
                             + ":"
                             + category
                             + ":"
                             + name);
            }
            val = getInitConfigValue(category, name);
        }

        if (val == null) {
            logger.error(
                         "Please set the init parameter for : "
                         + moduleName
                         + ":"
                         + category
                         + ":"
                         + name);
        }
        map.put(name, val);
        return val;
    }

    public String[] getConfigValues(String category, String name) {
        // First get from settings table
        Map map = getConfigTable(category, settings);
        String[] val = (String[]) map.get(name);
        if (val != null)
            return val;

        // if the cache doesn't have it yet, get from the database
        try {
            val =
                (String[]) BoxConfigUtil.getModuleConfigValues(
                                                           moduleName,
                                                           category,
                                                           name);
        } catch (Exception e) {
            val = null;
        }
        if (val == null) {
            // not in databse then we will use the init value
            val = getInitConfigValues(category, name);
            if (val == null) {
                logger.error(
                             "Please set the init parameter for : "
                             + moduleName
                             + ":"
                             + category
                             + ":"
                             + name);
            }
        }
        map.put(name, val);
        return val; // null is possible for String[] settings.
    }

    public boolean setConfigValue(String category, String name, String value) {
        try {
            Map map = getConfigTable(category, settings);
            BoxConfigUtil.setModuleConfigValue(moduleName, category, name, value);
            map.remove(name);
            map.put(name, value);
        } catch (Exception e) {
            if (logger.isDebugEnabled())
                logger.debug(
                             "Error set config:" + moduleName + category + name);
            return false;
        }
        return true;
    }

    public boolean setConfigValues(
                                   String category,
                                   String name,
                                   String[] value) {
        try {
            Map map = getConfigTable(category, settings);
            BoxConfigUtil.setModuleConfigValues(moduleName, category, name, value);
            map.remove(name);
            map.put(name, value);
        } catch (Exception e) {
            if (logger.isDebugEnabled())
                logger.debug(
                             "Error set config:" + moduleName + category + name);
            return false;
        }
        return true;
    }

    protected Map getConfigTable(String category, Map map) {
        Map val = (Map) map.get(category);
        if (val == null) {
            val = new HashMap();
            map.put(category, val);
        }
        return val;
    }

    public String toString() {
        StringBuffer str = new StringBuffer();
        str.append("moduleName=");
        str.append(moduleName);
        str.append(", ");
        str.append("category=");
        str.append(category);
        str.append(", ");
        str.append("version=");
        str.append(version);
        str.append(", ");
        str.append("required=");
        str.append(required);
        str.append(", ");
        str.append("fullName=");
        str.append(fullName);
        str.append(", ");
        str.append("url=");
        str.append(url);
        str.append(", ");
        str.append("icon=");
        str.append(icon);
        return str.toString();
    }
}
